package repository

import (
	"context"
	"gitee.com/DonHz/basic-go/webook/internal/domain"
	"gitee.com/DonHz/basic-go/webook/internal/repository/cache"
	"gitee.com/DonHz/basic-go/webook/internal/repository/dao"
	"gitee.com/DonHz/basic-go/webook/pkg/logger"
	"strconv"
)

var ErrRecordNotFound = dao.ErrRecordNotFound

type InteractiveRepository interface {
	IncrReadCnt(ctx context.Context, biz string, bizId int64) error
	BatchIncrReadCnt(ctx context.Context, bizs []string, bizIds []int64) error
	IncrLike(ctx context.Context, biz string, bizId int64, uid int64) error
	DecrLike(ctx context.Context, biz string, bizId int64, uid int64) error
	AddCollectItem(ctx context.Context, biz string, bizId int64, cid int64, uid int64) error
	Get(ctx context.Context, biz string, bizId int64, uid int64) (domain.Interactive, error)
	Liked(ctx context.Context, biz string, bizId int64, uid int64) (bool, error)
	Collected(ctx context.Context, biz string, bizId int64, uid int64) (bool, error)
	toDomain(intr dao.Interactive) domain.Interactive
	AddHistoryRead(ctx context.Context, aid int64, uid int64) error
}

type CachedReadCntRepository struct {
	dao   dao.InteractiveDAO
	cache cache.InteractiveCache
	l     logger.Logger
}

func NewCachedInteractiveRepository(dao dao.InteractiveDAO, cache cache.InteractiveCache,
	l logger.Logger) InteractiveRepository {
	return &CachedReadCntRepository{
		dao:   dao,
		cache: cache,
		l:     l,
	}
}

func (c *CachedReadCntRepository) IncrReadCnt(ctx context.Context, biz string, bizId int64) error {
	err := c.dao.IncrReadCnt(ctx, biz, bizId)
	if err != nil {
		return err
	}
	return c.cache.IncrReadCntIfPresent(ctx, biz, bizId)
}

func (c *CachedReadCntRepository) BatchIncrReadCnt(ctx context.Context, bizs []string, bizIds []int64) error {
	err := c.dao.BatchIncrReadCnt(ctx, bizs, bizIds)
	if err != nil {
		return nil
	}
	return c.cache.BatchIncrReadCntIfPresent(ctx, bizs, bizIds)
}

func (c *CachedReadCntRepository) IncrLike(ctx context.Context, biz string, bizId int64, uid int64) error {
	// 先插入点赞，再更新点赞数，最后更新缓存
	err := c.dao.InsertLikeInfo(ctx, biz, bizId, uid)
	if err != nil {
		return err
	}
	return c.cache.IncrLikeCntIfPresent(ctx, biz, bizId, uid)
}

func (c *CachedReadCntRepository) DecrLike(ctx context.Context, biz string, bizId int64, uid int64) error {
	// 先插入点赞，再更新点赞数，最后更新缓存
	err := c.dao.DeleteLikeInfo(ctx, biz, bizId, uid)
	if err != nil {
		return err
	}
	return c.cache.DecrLikeCntIfPresent(ctx, biz, bizId, uid)
}

func (c *CachedReadCntRepository) AddCollectItem(ctx context.Context,
	biz string, bizId int64, cid int64, uid int64) error {
	err := c.dao.InsertCollectionBiz(ctx, dao.UserCollectionBiz{
		Cid:   cid,
		Biz:   biz,
		BizId: bizId,
		Uid:   uid,
	})
	if err != nil {
		return err
	}
	// 收藏个数（有多少人收藏了 biz+bizId)
	return c.cache.IncrCollectCntIfPresent(ctx, biz, bizId)
}

func (c *CachedReadCntRepository) Get(ctx context.Context, biz string, bizId int64, uid int64) (domain.Interactive, error) {
	// 要从缓存中拿阅读数、点赞数和收藏数
	intr, err := c.cache.Get(ctx, biz, bizId)
	if err == nil {
		return intr, nil
	}
	daoIntr, err := c.dao.Get(ctx, biz, bizId)
	if err != nil {
		return domain.Interactive{}, err
	}
	intr = c.toDomain(daoIntr)
	go func() {
		er := c.cache.Set(ctx, biz, bizId, intr)
		if er != nil {
			c.l.Error("回写缓存失败",
				logger.String("biz: ", biz),
				logger.String("bizId: ", strconv.FormatInt(bizId, 10)),
				logger.Error(er))
		}
	}()
	return intr, nil
}

func (c *CachedReadCntRepository) Liked(ctx context.Context, biz string, bizId int64, uid int64) (bool, error) {
	_, err := c.dao.GetLikeInfo(ctx, biz, bizId, uid)
	switch err {
	case nil:
		return true, nil
	case ErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (c *CachedReadCntRepository) Collected(ctx context.Context, biz string, bizId int64, uid int64) (bool, error) {
	_, err := c.dao.GetCollectionInfo(ctx, biz, bizId, uid)
	switch err {
	case nil:
		return true, nil
	case ErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (c *CachedReadCntRepository) AddHistoryRead(ctx context.Context, aid int64, uid int64) error {
	return c.dao.InsertReadHistory(ctx, aid, uid)
}

func (c *CachedReadCntRepository) toDomain(intr dao.Interactive) domain.Interactive {
	return domain.Interactive{
		LikeCnt:    intr.LikeCnt,
		CollectCnt: intr.CollectCnt,
		ReadCnt:    intr.ReadCnt,
	}
}
